<template>
  <div class="basic-tabs">
    <el-tabs
      v-model="activeTab"
      :class="{
        'art-full-height': fullHeight,
        'el-tabs--custom-card': type === 'custom-card'
      }"
      :type="type === 'custom-card' ? '' : type"
      v-bind="$attrs"
    >
      <template #[item]="data" v-for="item in Object.keys($slots)">
        <slot :name="item" v-bind="data"></slot>
      </template>

      <el-tab-pane
        v-for="tab in filterTabs"
        :key="tab.name"
        :lazy="allLazy"
        :class="tab.isFit ? 'is-fit' : ''"
        v-bind="getPaneProps(tab)"
      >
        <template v-for="(v, k) in tab.slots" #[k]="data">
          <template v-if="isForceRender(k, tab)">
            <template v-if="isComponentRenderer(v)">
              <!-- <Suspense :key="k"> -->
              <component
                :key="k"
                :is="renderComponent(v, tab)"
                :class="tab.isFit ? 'is-fit' : ''"
                v-bind="data"
              />
              <!-- <template #fallback> 加载中... </template> -->
              <!-- </Suspense> -->
            </template>

            <div v-else-if="isString(v)" :key="k" :class="tab.isFit ? 'is-fit' : ''">
              <slot :name="v" v-bind="data"></slot>
            </div>
          </template>
        </template>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>

<script lang="ts" setup>
  import { isVNode } from 'vue'
  import { TabsProps } from 'element-plus'
  import { isPlainObject, isString, isFunction, omit } from 'lodash-es'
  import { useUserStore } from '@/store/modules/user'
  import {
    onMountedOrActivated,
    onUnMountedOrDeactivated
  } from '@/composables/onMountedOrActivated'
  import { mittBus } from '@/utils/sys'
  import { TabPaneItem } from './types'

  const props = defineProps({
    modelValue: {
      type: [String, Number],
      default: ''
    },
    type: {
      // el原始type为 ''
      type: String as PropType<TabsProps['type'] | 'custom-card'>,
      default: 'custom-card'
    },
    tabs: {
      type: Array as PropType<TabPaneItem[]>,
      required: true
    },
    /** 是否延迟渲染,el默认的是单个挂载到pane上,此处统一处理 */
    allLazy: {
      type: Boolean,
      default: true
    },
    /** 被隐藏时是否渲染 DOM 结构,即只渲染当前页, el没有离开时销毁的api 参考antDesign */
    allForceRender: {
      type: Boolean,
      default: false
    },
    /** 当activeTab不存在列表中时,是否渲染第一个tab */
    autoFirstActive: {
      type: Boolean,
      default: true
    },
    /** id 用于刷新后保持tab状态,默认为路由路径 */
    id: {
      type: String,
      default: ''
    },
    /** 是否保持tab状态 传参为 refresh 时, 页面刷新按钮触发后保持tab状态(非浏览器刷新按钮) */
    keepStateType: {
      type: [Storage, Boolean, String] as PropType<Storage | 'refresh' | false>,
      default: 'refresh'
    },
    /** 是否占满页面显示 */
    fullHeight: {
      type: Boolean,
      default: false
    }
  })

  const emits = defineEmits(['update:modelValue'])

  const CACHE_KEY = 'page-tab-cache'
  const route = useRoute()
  const userStore = useUserStore()
  const roles = userStore.info.roles
  const quanXian = userStore.getSycConstant('quanXian')

  const activeTab = computed({
    get: () => {
      const modelValue = props.modelValue
      if (props.autoFirstActive && !filterTabs.value.some((item) => item.name === modelValue)) {
        // tab可能是异步,默认可能没有[0]
        return filterTabs.value[0]?.name
      } else {
        return modelValue
      }
    },
    set: (value: TabPaneItem['name']) => {
      emits('update:modelValue', value)
      setTabCache(value)
    }
  })

  const filterPermission = (tab: TabPaneItem) => {
    // 同时判断角色和权限码
    return !tab.roles || tab.roles.some((role) => roles?.includes(role) || quanXian.includes(role))
  }

  const filterTabs = computed(() => props.tabs.filter(filterPermission))

  // #region 渲染相关

  // 判断是否渲染
  const isForceRender = (slotName: string, tab: TabPaneItem) => {
    return !(props.allForceRender || tab.forceRender)
      ? slotName !== 'default' || activeTab.value === tab.name
      : true
  }

  // 判断是否被渲染器解析
  const isComponentRenderer = (v) => {
    return isComponent(v) || isVNode(v) || isFunction(v) || isPlainObject(v)
  }

  // 判断是否组件
  const isComponent = (value: any): boolean => {
    return isPlainObject(value) && ('render' in value || 'setup' in value || 'template' in value)
  }

  const getPaneProps = (item: TabPaneItem): TabPaneItem => {
    return omit(item, ['roles', 'forceRender', 'isFit', 'slots'])
  }

  // function isAsyncFunction(func) {
  //   return Object.prototype.toString.call(func) === '[object AsyncFunction]'
  // }

  // 处理渲染
  const renderComponent = (v: any, tab: TabPaneItem) => {
    // 对象 vnode也有component属性 需要区分
    if (isPlainObject(v) && !isVNode(v) && 'component' in v) {
      const component = v.component
      const props = v.props || {}

      // 函数
      if (isFunction(component)) {
        return renderAsyncComponent(component, props, tab)
      }
      // 组件或者vnode
      return h(component, unref(props))
    }

    // 函数
    if (isFunction(v)) {
      return renderAsyncComponent(v)
    }
    // 组件或者vnode
    return v
  }

  // 防止异步导入组件重复加载
  const asyncComponentCache = new WeakMap()

  const renderAsyncComponent = (component: any, props: any = {}, tab?: TabPaneItem) => {
    const hasProps = Object.keys(props).length > 0
    const cacheKey = component
    const result = component()
    // 如果是动态导入的结果，需要用 defineAsyncComponent 包装一下
    if (result && isFunction(result.then)) {
      const asyncComponent = asyncComponentCache.get(cacheKey) || defineAsyncComponent(component)
      asyncComponentCache.set(cacheKey, asyncComponent)
      return hasProps ? h(asyncComponent, props) : asyncComponent
    } else {
      if (hasProps) {
        console.error(`name ${tab?.name}:对象传参时,非异步组件不能使是一个函数,会造成props丢失`)
      }
      return component
    }
  }

  // #endregion 渲染相关

  // #region  缓存相关

  // 返回缓存方法 false 则不缓存
  const getCacheFn = computed(() => {
    let fn = props.keepStateType

    if (fn === localStorage || fn === sessionStorage) {
      //
    } else if (fn === 'refresh') {
      fn = sessionStorage
    } else {
      fn = false
    }

    return fn
  })

  // 是否页面刷新按钮触发(非浏览器刷新按钮)
  const getIsRefreshCache = computed(() => {
    return props.keepStateType === 'refresh'
  })

  // 不传id则返回整个对象
  function getTabCache(id?: string) {
    const cacheFn = getCacheFn.value
    if (cacheFn) {
      const cache = JSON.parse(cacheFn.getItem(CACHE_KEY) || '{}')
      return id ? (cache[id] as TabPaneItem['name']) : (cache as Recordable<TabPaneItem['name']>)
    }
  }

  const setTabCache = (value: TabPaneItem['name']) => {
    const cacheFn = getCacheFn.value
    if (cacheFn) {
      const id = props.id || route.fullPath
      const cache = getTabCache()
      cache![id] = value
      cacheFn.setItem(CACHE_KEY, JSON.stringify(cache))
    }
  }

  // 处理重新获取tab缓存
  const handleTabCache = () => {
    const id = props.id || route.fullPath
    if (id) {
      const cache = getTabCache(id) as TabPaneItem['name']
      cache && (activeTab.value = cache)
    }
  }

  // refresh 类型需要即时销毁事件,避免开启keepAlive时跨页面被触发
  if (getIsRefreshCache.value) {
    onMountedOrActivated(() => {
      // tabs会跟随页面刷新,此时mittBus内的vue实例已经不存在, 使用afterRefresh延迟事件
      mittBus.on('afterRefresh', handleTabCache)
    })

    onUnMountedOrDeactivated(() => {
      mittBus.off('afterRefresh', handleTabCache)
    })
  } else {
    // Storage 类型在onMounted时执行
    onMounted(() => {
      handleTabCache()
    })
  }

  // #endregion  缓存相关
</script>

<style lang="scss" scoped>
  .basic-tabs {
    .is-fit,
    // 穿透到子组件
    :deep(.is-fit) {
      display: flex;
      flex: 1;
      flex-direction: column;
      height: 100%;

      :deep(.el-card) {
        width: 100%;
      }
    }

    :deep(.el-tabs) {
      // 系统type样式
      // 避免嵌套使用影响子组件
      &.el-tabs--custom-card > .el-tabs__header {
        $padding: 18px;

        --el-tabs-header-height: 36px;

        margin: 0 0 var(--card-margin);

        .el-tabs__nav-wrap {
          &::after {
            display: none;
          }

          .el-tabs__nav {
            // overflow: hidden;
            border: 1px solid var(--main-color);
            // 跟随系统圆角
            border-radius: calc(var(--custom-radius) / 2 + 2px) !important;
          }

          .el-tabs__item {
            padding: 0 $padding;
            font-family: 'Source Han Sans CN-Regular', sans-serif;
            font-size: 14px;
            font-weight: 400;
            line-height: 22px;
            color: #4f5759;

            &.is-active {
              font-family: 'Source Han Sans CN-Medium', sans-serif;
              // TODO 会将别的项挤一下暂时注释
              // font-size: 16px;
              font-weight: 500;
              color: var(--el-color-white);
              // 跟随bar动画,避免bar还没有移动到对应item时,item颜色突变
              transition: color var(--el-transition-duration)
                var(--el-transition-function-ease-in-out-bezier);
              // transition: font-size var(--el-transition-duration)
              //   var(--el-transition-function-ease-in-out-bezier);
            }
          }

          .el-tabs__active-bar {
            left: -$padding;
            z-index: -1;
            height: 100%;
            padding: 0 $padding;
            background-color: var(--main-color);
            // 跟随系统圆角 bar圆角小一点 避免在边缘时和外部圆角不重合有空白
            border-radius: calc(var(--custom-radius) / 2) !important;
          }
        }
      }

      // 原始type样式
      .el-tabs__header {
        $padding: 29px;

        --el-tabs-header-height: 51px;

        .el-tabs__item {
          padding: 0 $padding;

          &:nth-child(2) {
            padding-left: $padding;
          }

          &:last-child {
            padding-right: $padding;
          }

          font-family: 'Source Han Sans CN-Regular', sans-serif;
          font-size: 14px;
          font-weight: 400;
          line-height: 22px;

          &.is-active {
            font-family: 'Source Han Sans CN-Medium', sans-serif;
            // TODO 会将别的项挤一下暂时注释
            // font-size: 16px;
            font-weight: 500;
          }
        }

        .el-tabs__active-bar {
          left: -$padding;
          height: 4px;
          padding: 0 29px;
        }
      }

      // 避免单表格页面hover阴影消失
      // TODO 会造成表格宽度自适应缩小异常
      .el-tabs__content {
        display: flex;
        flex-direction: column;
        overflow: visible;
        // .el-tab-pane {
        //   overflow: hidden;
        // }
      }
    }
  }
</style>
